// Copyright 2022 Jeroen van Nugteren

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// include header
#include "dfcircle.hh"

// common headers
#include "rat/common/error.hh"

// code specific to Rat
namespace rat{namespace dm{

	// constructcor
	DFCircle::DFCircle(){
		set_name("Circle");
	}

	// constructcor
	DFCircle::DFCircle(const fltp radius, const fltp xc, const fltp yc) : DFCircle(){
		set_radius(radius); set_xc(xc); set_yc(yc);
	}

	// factory
	ShDFCirclePr DFCircle::create(){
		return std::make_shared<DFCircle>();
	}

	// factory
	ShDFCirclePr DFCircle::create(const fltp radius, const fltp xc, const fltp yc){
		return std::make_shared<DFCircle>(radius,xc,yc);
	}


	// setters
	void DFCircle::set_radius(const fltp radius){
		radius_ = radius;
	}

	void DFCircle::set_xc(const fltp xc){
		xc_ = xc;
	}

	void DFCircle::set_yc(const fltp yc){
		yc_ = yc;
	}

	// getters
	fltp DFCircle::get_radius()const{
		return radius_;
	}

	fltp DFCircle::get_xc()const{
		return xc_;
	}

	fltp DFCircle::get_yc()const{
		return yc_;
	}

	// get bounding box
	arma::Mat<fltp>::fixed<2,2> DFCircle::get_bounding() const{
		// allocate
		arma::Mat<fltp>::fixed<2,2> Mb;
		
		// bounding in x
		Mb.col(0) = arma::Col<fltp>::fixed<2>{xc_-radius_,xc_+radius_};
		
		// bounding in y
		Mb.col(1) = arma::Col<fltp>::fixed<2>{yc_-radius_,yc_+radius_};

		// return box
		return Mb;
	}

	// distance function
	arma::Col<fltp> DFCircle::calc_distance(const arma::Mat<fltp> &p) const{
		return arma::hypot(p.col(0)-xc_, p.col(1)-yc_) - radius_;
	}



	// perimeter function
	ShPerimeterPr DFCircle::create_perimeter(const fltp delem) const{
		// calculate number of elements
		const arma::uword num_elements = std::max(arma::uword(std::ceil(2*arma::Datum<fltp>::pi*radius_/delem)),2llu);

		// create node coordinates on a circle
		const arma::Row<fltp> theta = arma::linspace<arma::Row<fltp> >(
			0,((static_cast<fltp>(num_elements)-1.0)/num_elements)*2*arma::Datum<fltp>::pi,num_elements);
		const arma::Row<fltp> rho(num_elements,arma::fill::value(radius_));
		const arma::Mat<fltp> Rn = arma::join_vert(rho%arma::sin(theta)+xc_, rho%arma::cos(theta)+yc_);

		// create element matrix
		const arma::Mat<arma::uword> n=arma::join_vert(
			arma::regspace<arma::Row<arma::uword> >(0,Rn.n_cols-1),
			arma::shift(arma::regspace<arma::Row<arma::uword> >(0,Rn.n_cols-1),-1,1));

	 	// create perimeter
		return Perimeter::create(Rn,n);
	}


	// validity check
	bool DFCircle::is_valid(const bool enable_throws)const{
		// check user input
		if(radius_<0){if(enable_throws){rat_throw_line("radius must be equal to or larger than zero");} return false;};
		return true;
	}


	// type string for serialization
	std::string DFCircle::get_type(){
		return "rat::dm::dfcircle";
	}

	// method for serialization into json
	void DFCircle::serialize(
		Json::Value &js, cmn::SList &list) const{
		
		// parent
		DistFun::serialize(js,list);

		// store type ID
		js["type"] = get_type();
		js["xc"] = xc_;	js["yc"] = yc_;
		js["radius"] = radius_;
	}

	// method for deserialisation from json
	void DFCircle::deserialize(
		const Json::Value &js, cmn::DSList &list, 
		const cmn::NodeFactoryMap &factory_list, 
		const boost::filesystem::path &pth){

		// parent
		DistFun::deserialize(js,list,factory_list,pth);

		// store type ID
		set_xc(js["xc"].ASFLTP()); 
		set_yc(js["yc"].ASFLTP());
		set_radius(js["radius"].ASFLTP());
	}

}}